package visualization
{
	import flash.events.Event;
	import flash.utils.Dictionary;
	
	import model.Field;
	import model.Model;
	import model.metrics.Metric;
	
	import mx.collections.IList;
	import mx.containers.Canvas;
	import mx.controls.Label;
	import mx.core.ScrollPolicy;
	import mx.core.UIComponent;
	import mx.effects.Move;
	import mx.effects.Resize;
	import mx.events.CollectionEvent;
	import mx.events.CollectionEventKind;
	import mx.events.PropertyChangeEvent;
	import mx.events.PropertyChangeEventKind;
	import mx.events.TweenEvent;
	import mx.utils.ObjectProxy;
	
	import visualization.operators.Operator;
	import visualization.renderers.VisualizationRenderer;

	public class Visualization extends Canvas implements IDataPoint
	{
		/*====================
		 * Variables
		 *====================
		 */
		
		// IDataPoint variables
		[Bindable] public var title:String = "Visualization";
		[Bindable] public var fields:Array = new Array();
		[Bindable] public var moveTween:Move;
		[Bindable] public var resizeTween:Resize;
		private var _model:Model;
		public function get model():Model{
			return this._model;
		}
		public function set model( mod:Model ):void{
			this._model = mod;
		}
		
		private var _source:Object;
		public function get source():Object{
			return this._source;
		}
		public function set source( obj:Object ):void{
			if(this._source is ObjectProxy){
				(this._source as ObjectProxy).removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, this.handlePropertyChange);
			}
			this._source = obj;
			if(this._source is ObjectProxy){
				(this._source as ObjectProxy).addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, this.handlePropertyChange);
			}
			this.update();
		}
		
		// Visualization variables
		private var _renderer:VisualizationRenderer;
		public function get renderer():VisualizationRenderer{
			return this._renderer;
		}
		public function set renderer( rend:VisualizationRenderer ):void{
			if(this._renderer != null){
				var stateA:Array = this._renderer.getLayout(this);
				var stateB:Array = rend.getLayout(this);
				for(var i:Number = 0; i<stateA.length; i++){
					var point:Object = stateA[i];
					var locTween:Move = (point.item as IDataPoint).moveTween;
					var sizeTween:Resize = (point.item as IDataPoint).resizeTween;
					locTween.xBy = 1;
					locTween.yBy = 1;
					sizeTween.heightBy = 1;
					sizeTween.widthBy = 1;
					locTween.xFrom = point.x;
					locTween.yFrom = point.y;
					sizeTween.heightFrom = point.height;
					sizeTween.widthFrom = point.width;
					point = stateB[i];
					locTween.xTo = point.x-point.width/2;
					locTween.yTo = point.y-point.height/2;
					sizeTween.heightTo = point.height;
					sizeTween.widthTo = point.width;
					if(i == stateA.length - 1){
						locTween.addEventListener(TweenEvent.TWEEN_END, this.refresh);
					}
					locTween.play();
					sizeTween.play();
				}
			}
			this._renderer = rend;
		}
		
		
		// Data Source
		private var _dataSource:IList = null;
		public function set dataSource( list:IList ):void{
			if(this._dataSource != null){
				this._dataSource.removeEventListener(CollectionEvent.COLLECTION_CHANGE,this.handleCollectionEvent);
			}
			this._dataSource = list;
			list.addEventListener(CollectionEvent.COLLECTION_CHANGE,this.handleCollectionEvent);
			this.addItems(list.toArray());
		}
		public function get dataSource():IList{
			return this._dataSource;
		}
		
		// Thumbnail Mode
		private var _thumbnailMode:Boolean = false;
		public function get thumbnailMode():Boolean{
			return this._thumbnailMode;
		}
		
		// Data Points
		private var _dataPoints:Array = new Array();
		public function get dataPoints():Array{
			return this._dataPoints;
		}
		
		// Metrics
		public var metrics:Array = new Array();
		public var metricData:Dictionary = new Dictionary();
		
		// Background drawing area
		public var background:UIComponent;
		
		/*====================
		 * Constructor
		 *====================
		 */
		public function Visualization()
		{
			super();
			this.moveTween = new Move(this);
			this.resizeTween = new Resize(this);
			this.background = new UIComponent();
			this.addChild(this.background);
		}
		
		/*====================
		 * Methods
		 *====================
		 */
		 // Methods for adding data
		 public function addItem( item:Object ):void{
		 	var point:IDataPoint = this._model.generatePoint(item);
		 	var points:Array = new Array();
		 	points.push(point);
		 	
		 	point.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,this.handlePropertyChange);
		 	
		 	this._dataPoints.push(point);
		 	this.addChild(point as UIComponent);
		 	
		 	
		 	this.dispatchEvent(new CollectionEvent(CollectionEvent.COLLECTION_CHANGE,false,false,CollectionEventKind.ADD,-1,-1,points));
		 	this.update();
		 }
		 public function addItems( items:Array ):void{
		 	var point:IDataPoint;
		 	var points:Array = new Array();
		 	for each( var item:Object in items){
		 		point = this._model.generatePoint(item);
		 		point.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,this.handlePropertyChange);
		 		this._dataPoints.push(point);
		 		points.push(point);
		 		this.addChild(point as UIComponent);
		 	}
		 	
		 	this.dispatchEvent(new CollectionEvent(CollectionEvent.COLLECTION_CHANGE,false,false,CollectionEventKind.ADD,-1,-1,points));
		 	this.update();
		 }
		 
		 public function getData( field:Field ):Object{
			return this._source[field.fieldName];
		 }
		 
		 // Updating metrics
		 public function updateMetrics():void{
		 	
		 	for each(var metric:Metric in this.metrics){
		 		metric.startMetric(this._dataPoints);
		 	}
		 	for each(var point:IDataPoint in this._dataPoints){
		 		for each(metric in this.metrics){
		 			metric.iterate(point);
		 		}
		 	}
		 	for each(metric in this.metrics){
		 		this.metricData[metric] = metric.finishMetric(this._dataPoints);
		 	}
		 }
		 
		 // Event Handeling
		 private function handleCollectionEvent( event:CollectionEvent ):void{
		 	if(event.kind == CollectionEventKind.ADD){
		 		this.addItems(event.items);
		 	}
		 	else{
		 		this.update();
		 	}
		 }
		 
		 public function handlePropertyChange( event:PropertyChangeEvent = null ):void{
		 	if(event != null) this.dispatchEvent(event);
		 	else{
		 		this.dispatchEvent(
		 			new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE,false,false,PropertyChangeEventKind.UPDATE)
		 		);
		 	}
		 	this.update();
		 }
		 
		 public function update():void{
		 	this.updateMetrics();
		 	this.refresh();
		 }
		 
		 // Rendering Utilities
		 public function renderThubmnail():void{
		 	this._thumbnailMode = true;
		 	this.verticalScrollPolicy = ScrollPolicy.OFF;
		 	this.horizontalScrollPolicy = ScrollPolicy.OFF;
		 	this.renderer.renderThumbnail(this);
		 }
		 
		 public function renderFull():void{
		 	this._thumbnailMode = false;
		 	this.verticalScrollPolicy = ScrollPolicy.AUTO;
		 	this.horizontalScrollPolicy = ScrollPolicy.AUTO;
		 	if(this.renderer != null) this.renderer.renderFull(this);
		 }
		 
		 public function refresh(event:Event = null):void{
		 	if(this._thumbnailMode){
		 		this.renderThubmnail();
		 	}
		 	else{
		 		this.renderFull();
		 	}
		 	for each(var datapoint:IDataPoint in this._dataPoints){
		 		if(datapoint is Visualization) (datapoint as Visualization).refresh();
		 	}
		 }
		 
		 public function setSubRenderer( renderer:VisualizationRenderer ):void{
		 	for each(var point:IDataPoint in this._dataPoints){
		 		if(point is Visualization){
		 			(point as Visualization).renderer = renderer;
		 			(point as Visualization).refresh();
		 		}
		 	}
		 }
		 
		 public function setSubVisibilty(value:Boolean):void{
		 	for each(var comp:Object in this.getChildren()){
		 		if(comp is Visualization){
		 			(comp as Visualization).setVisible(value);
		 		}
		 	}
		 }
		 
		 // Querying for data
		 public function getValue( descriptor:Object, element:IDataPoint = null ):*{
			if( descriptor is Number){
				return descriptor as Number;
			}
			else if(descriptor is Field){
				return element.source[(descriptor as Field).fieldName];
			}
			else if(descriptor is Metric){
				return this.metricData[(descriptor as Metric)];
			}
			else if( descriptor is String ){
				return descriptor as String;
			}
			else if( descriptor is Operator){
				return (descriptor as Operator).process(this,element);
			}
			else{
				return descriptor;
			}
		}
		
		public function getTitle( descriptor:Object ):String{
			if(descriptor is Field){
				return (descriptor as Field).title;
			}
			else if(descriptor is Metric){
				return (descriptor as Metric).title;
			}
			else if (descriptor is Operator){
				return (descriptor as Operator).title;
			}
			else{
				return descriptor.toString();
			}
		}
		
		// Management of labels
		private var _labelIndex:Number = 0;
		private var _labels:Array = new Array();
		public function resetLabels():void{
			this._labelIndex = 0;
			for each(var lab:Label in this._labels){
				lab.visible = false;
			}
		}
		public function getLabel():Label{
			var lab:Label;
			if(this._labels.length > this._labelIndex){
				lab = this._labels[this._labelIndex];
				lab.visible = true;
				this._labelIndex++;
			}
			else{
				lab = new Label();
				this.addChild(lab);
				this._labels.push(lab);
			}
			return lab;
		}
	}
}